package engine.objects;

import engine.game.Manager;
import engine.graphics.Vertex2f;
import engine.system.EDirection;
import engine.system.MADFrameRate;
import engine.system.Timer;
import static org.lwjgl.opengl.GL11.*;

import game.objects.nonstaticObjects.*;

@SuppressWarnings("unused")
public class PhysicsObject extends TexturedObject {

	private static final long serialVersionUID = 1L;
	
	protected static final float G = 0.0981f ; 
	
	
	protected ObjectCollision objectCollision = ObjectCollision.getDefaultObjectCollision() ; 
	
	private boolean isCollidable = true ;
	private boolean isStatic = false ; 
	private boolean isFalling = false ; 
	private boolean isJumping = false ;  
	private boolean canBounce = false ; 
	private boolean isCollectable = false ; 
	
	private boolean canUpdate = true ; 
	
	private Timer jumpingTimer ; 
	private Timer fallingTimer ; 
	private Timer globalTimer ; 
	protected int jumpingSpeed = 0 ; 
	protected int fallingSpeed = 0 ; 
	protected float JUMPING_MULT = 2.5f;  

	private int bouncingAmt ; 
	private EBouncing bouncing ; 
	
	private int speed = 3 ; 
	
	public PhysicsObject(Manager m, int x, int y, int w, int h) {
		super(m, x, y, w, h);
		fallingTimer = new Timer(); 
		jumpingTimer = new Timer(); 
		this.globalTimer = new Timer(); 
		this.globalTimer.start(); 
	}
	public PhysicsObject(Manager m, int x, int y, int w, int h, boolean isCollidable) {
		this(m,x,y,w,h); 
		this.isCollidable = isCollidable ; 
	}
	public PhysicsObject(Manager m, int x, int y, int w, int h, boolean collidable, boolean isStatic) {
		this(m,x,y,w,h, collidable); 
		this.isStatic = isStatic ; 
	}
	
	public void stopAllTimers(){
		this.jumpingTimer.pause(); 
		this.fallingTimer.pause(); 
		this.globalTimer.pause(); 
	}
	
	public void continueAllTimers(){
		this.jumpingTimer.continueTimer(); 
		this.fallingTimer.continueTimer(); 
		this.globalTimer.continueTimer(); 
	}
	
	public int getSpeed() {
		return speed;
	}

	public void setSpeed(int speed) {
		this.speed = speed;
	}

	public void setObjectCollision(ObjectCollision col){
		this.objectCollision = col ;
	}
	
	public ObjectCollision getObjectCollision(){
		return this.objectCollision; 
	}
	
	public Timer getFallingTimer(){
		return this.fallingTimer ;
	}
	
	public Timer getJumpingTimer(){
		return this.jumpingTimer; 
	}
	
	public Timer getGlobalTimer(){
		return this.globalTimer ; 
	}
	
	public void enableFalling(){
		this.isFalling = true ;
	}

	public void disableFalling(){
		this.isFalling = false ;
	}

	public boolean isFalling() {
		return isFalling ; 
	}
	
	public void enableCollisions(){
		this.isCollidable = true ; 
	}
	
	public void disableCollisions(){
		this.isCollidable = false ; 
	}
	
	public boolean isCollidable(){
		return this.isCollidable; 
	}

	public void enableStatic(){
		this.isStatic = true ; 
	}

	public void disableStatic(){
		this.isStatic = false ; 
	}
	
	public boolean isStatic(){
		return this.isStatic ; 
	}

	public void enableJumping(){
		if(!this.isFalling())
			this.isJumping = true ; 
	}

	public void disableJumping(){
		this.isJumping = false ; 
	}
	
	public boolean isJumping(){
		return this.isJumping ; 
	}
		
	public void enableBouncing(EBouncing bouncing){
		this.canBounce = true ; 
		this.bouncing = bouncing ; 
	}

	public void disableBouncing(){
		this.canBounce = false ; 
	}
	
	public boolean canBounce(){
		return this.canBounce ; 
	}
	
	public void disableUpdating(){
		this.canUpdate = false ; 
	}
	
	public void enableUpdating(){
		this.canUpdate = true ; 
	}
	
	public boolean canUpdate(){
		return this.canUpdate ; 
	}
	
	public boolean hasObjectCollision(){
		return this.objectCollision != null && this.objectCollision.isColliding(); 
	}
	
	public boolean collisionCriteriaGiven(){
		return this.objectCollision.isColliding();
	}
	
	public void update(){
		if(this.canUpdate()){
			this.jump(); 
			this.fall(); 
			super.update(); 
		}
	}
	
	public void fall() {
		if(this.canUpdate()){
			if(!this.isStatic()){
				if (!isFalling)
					fallingTimer.start();
				fallingSpeed = (int) ((int) Math.round(fallingTimer.getAbsoluteDelta()) / speed * G);
				getManager().getGameObjects().colliding(this, new Vertex2f(0, -fallingSpeed));
				if (!this.collisionCriteriaGiven()) {
					move(EDirection.DOWN, fallingSpeed);
					isFalling = true;
				} else {
					isFalling = false ; 
					if(this.canBounce()){
						if (this.bouncingAmt < this.bouncing.getBouncingFactor()) {
							this.bouncingAmt++;
							this.enableJumping();
						} else if (this.bouncingAmt > this.bouncing
								.getBouncingFactor()) {
							this.bouncingAmt = 0;
							this.disableJumping();
						}	
					}			
				}
			}
		}
	}
	
	public void jump(){
		if(this.canUpdate()){
			if(this.isJumping()){
				this.jumpingSpeed = (int) (this.getSpeed() * (JUMPING_MULT) - G * jumpingTimer.getAbsoluteDelta() / this.getSpeed()); 
				if(this.canBounce() && this.bouncingAmt > 0){
					this.jumpingSpeed /= this.bouncingAmt ; 
					this.jumpingSpeed *= this.bouncing.getBounciness(); 
				}
				if(this.jumpingSpeed>0){
					this.disableFalling(); 
					this.move(EDirection.UP, this.jumpingSpeed);
				}else{
					this.enableFalling(); 
					this.disableJumping();
				}
			}else
				jumpingTimer.start(); 
		}
	}
	
	public void render(){
		if(this.canUpdate()){
			if(!this.isStatic || !this.displayListEnabled() || this.hasAnimation()){
				super.render();
			}else{
				if(this.isFirstRendering()){
					this.displayList = glGenLists(1); 
					glNewList(this.displayList, GL_COMPILE);
					super.render(); 
					glEndList(); 
				}
				else
					glCallList(this.displayList);
			}
		}
	}
	
	public void move(EDirection dir){
		move(dir, speed); 
	}
	
	public void move(EDirection dir, int speed){
		switch(dir){
		case LEFT: moveLeft(speed); break ; 
		case RIGHT: moveRight(speed); break ; 
		case UP: moveUp(speed); break ; 
		case DOWN: moveDown(speed); break ;  
		}
	}

	protected void moveLeft(int speed) { 
		this.moveByDelta(new Vertex2f(-speed,0)); 
	}

	protected void moveRight(int speed) {
		this.moveByDelta(new Vertex2f(speed, 0)); 
	}

	protected void moveUp(int speed) {
		this.moveByDelta(new Vertex2f(0, speed)); 
	}

	protected void moveDown(int speed) {
		this.moveByDelta(new Vertex2f(0, -speed)); 
	}
	
	protected void moveByDelta(Vertex2f velocity){
//		float delta = (this.globalTimer.getRelativeDelta() / 1000.0f) ;
		float x = velocity.getX(), y = velocity.getY() ;  
//		x *= (delta);
//		y *= (delta) ;
		velocity = new Vertex2f(x,y);
		getManager().getGameObjects().colliding(this, velocity); 
		if(!this.collisionCriteriaGiven())
			this.setBoundsByVelocity(velocity); 
	}
	
	public void setBoundsByVelocity(Vertex2f velocity){
		int x = (int) this.getMinX(); 
		int y = (int) this.getMinY(); 
		this.setBounds((int)(x+velocity.getX()), (int)(y+velocity.getY()),
					this.getObjectWidth(), this.getObjectHeight()); 
	}

	public void setBounds(double x, double y, double width, double height) {
		super.setBounds((int)x, (int)y, (int)width, (int)height) ;
	}

	private void growMinX(int x) {
		this.setBounds((int)this.getMinX()+x,(int)this.getMinY(),
				this.getObjectWidth(), this.getObjectHeight()); 
	}
	private void growMaxX(int x) {
		this.setBounds((int)this.getMinX(),(int)this.getMinY(),
				this.getObjectWidth()+x, this.getObjectHeight()); 
	}
	private void growMinY(int y) {
		this.setBounds((int)this.getMinX(),(int)this.getMinY()+y,
				this.getObjectWidth(), this.getObjectHeight()); 
	}
	private void growMaxY(int y) {
		this.setBounds((int)this.getMinX(),(int)this.getMinY(),
				this.getObjectWidth(), this.getObjectHeight()+y); 
	}
	private void shrinkMinX(int x) {
		this.setBounds((int)this.getMinX()-x,(int)this.getMinY(),
				this.getObjectWidth(), this.getObjectHeight()); 
	}
	private void shrinkMaxX(int x) {
		this.setBounds((int)this.getMinX(),(int)this.getMinY(),
				this.getObjectWidth()-x, this.getObjectHeight()); 
	}
	private void shrinkMinY(int y) {
		this.setBounds((int)this.getMinX(),(int)this.getMinY()-y,
				this.getObjectWidth(), this.getObjectHeight()); 
	}
	private void shrinkMaxY(int y) {
		this.setBounds((int)this.getMinX(),(int)this.getMinY(),
				this.getObjectWidth(), this.getObjectHeight()-y); 
	}
	
}
